﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using UTable.Objects.Controls;
using UTable.Input.MultiTouch;
using System.Windows.Ink;
using System.Windows;
using UTable.Objects.Handlers.Gestures;
using System.Windows.Shapes;
using System.Windows.Media;

namespace UTable.Objects.Controls
{
    public class UPaintCanvas: InkCanvas, IControl
    {
        public UPaintCanvas()
        {
            InitializeIControlPart();
            this.InputReceived += new UTable.Input.InputEventHandler(UPaintCanvas_InputReceived);

            this.EditingModeChanged += new RoutedEventHandler(UPaintCanvas_EditingModeChanged);
            this.SizeChanged += new SizeChangedEventHandler(UPaintCanvas_SizeChanged);
            this.Loaded += new RoutedEventHandler(UPaintCanvas_Loaded);
            this.DefaultDrawingAttributes.FitToCurve = true;
            this.DefaultDrawingAttributes.Width = 3;
            this.DefaultDrawingAttributes.Height = 4;
        }

        void UPaintCanvas_Loaded(object sender, RoutedEventArgs e)
        {
            if (EnableSelection)
            {
                LocalGestureEngine engine = new LocalGestureEngine(this, this);

                OneContactGestureRecognizer recognizer1 = new OneContactGestureRecognizer();
                recognizer1.EnabledGestures.Add(OneContactGestureType.Tap);
                recognizer1.EnabledGestures.Add(OneContactGestureType.Translate);

                TwoContactsGestureRecognizer recognizer2 = new TwoContactsGestureRecognizer();
                recognizer2.EnabledGestures.Add(TwoContactsGestureType.Zoom);
                recognizer2.EnabledGestures.Add(TwoContactsGestureType.Rotate);

                engine.RegisterGesture(recognizer1);
                engine.RegisterGesture(recognizer2);

                engine.GestureDetected += new GestureDetectedEventHandler(OnGestureDetected);
                this.MultiTouchProcessor.Handlers.Add(engine);
            }
        }

        #region Grid

        List<Line> horizontalGridLines = new List<Line>();
        List<Line> verticalGridLines = new List<Line>();

        private void UPaintCanvas_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            UpdateGridLayout();
        }

        private void UpdateGridLayout()
        {
            // first remove all grid lines
            foreach (Line line in horizontalGridLines)
                this.Children.Remove(line);
            horizontalGridLines.Clear();
            foreach (Line line in verticalGridLines)
                this.Children.Remove(line);
            verticalGridLines.Clear();

            // now calculate and add the grid lines
            double y = GridInterval;

            while (y < this.ActualHeight)
            {
                Line line = new Line();
                line.X1 = 0;
                line.Y1 = y;
                line.X2 = this.ActualWidth;
                line.Y2 = y;
                this.Children.Add(line);
                horizontalGridLines.Add(line);

                y += GridInterval;
            }

            double x = GridInterval;
            while (x < this.ActualWidth)
            {
                Line line = new Line();
                line.X1 = x;
                line.Y1 = 0;
                line.X2 = x;
                line.Y2 = ActualHeight;
                this.Children.Add(line);
                verticalGridLines.Add(line);

                x += GridInterval;
            }
            UpdateGridAppearance();
        }

        private void UpdateGridAppearance()
        {
            foreach (Line line in horizontalGridLines)
            {
                if (ShowHorizontalGrid)
                    line.Visibility = Visibility.Visible;
                else
                    line.Visibility = Visibility.Collapsed;
                line.Stroke = GridStroke;
                line.StrokeThickness = GridThickness;
            }
            foreach (Line line in verticalGridLines)
            {
                if (ShowVertialGrid)
                    line.Visibility = Visibility.Visible;
                else
                    line.Visibility = Visibility.Collapsed;
                line.Stroke = GridStroke;
                line.StrokeThickness = GridThickness;
            }
        }

        #endregion

        #region Properties

        private static void OnGridAppearanceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            (obj as UPaintCanvas).UpdateGridAppearance();
        }

        private static void OnGridLayoutChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            (obj as UPaintCanvas).UpdateGridLayout();
        }

        public bool ShowHorizontalGrid
        {
            get { return (bool)GetValue(ShowHorizontalGridProperty); }
            set { SetValue(ShowHorizontalGridProperty, value); }
        }

        public static readonly DependencyProperty ShowHorizontalGridProperty =
            DependencyProperty.Register("ShowHorizontalGrid", typeof(bool), typeof(UPaintCanvas), new UIPropertyMetadata(true, new PropertyChangedCallback(OnGridAppearanceChanged)));


        public bool ShowVertialGrid
        {
            get { return (bool)GetValue(ShowVertialGridProperty); }
            set { SetValue(ShowVertialGridProperty, value); }
        }

        public static readonly DependencyProperty ShowVertialGridProperty =
            DependencyProperty.Register("ShowVertialGrid", typeof(bool), typeof(UPaintCanvas), new UIPropertyMetadata(false, new PropertyChangedCallback(OnGridAppearanceChanged)));


        public double GridThickness
        {
            get { return (double)GetValue(GridThicknessProperty); }
            set { SetValue(GridThicknessProperty, value); }
        }

        public static readonly DependencyProperty GridThicknessProperty =
            DependencyProperty.Register("GridThickness", typeof(double), typeof(UPaintCanvas), new UIPropertyMetadata(2.0, new PropertyChangedCallback(OnGridAppearanceChanged)));

        public double GridInterval
        {
            get { return (double)GetValue(GridIntervalProperty); }
            set { SetValue(GridIntervalProperty, value); }
        }

        public static readonly DependencyProperty GridIntervalProperty =
            DependencyProperty.Register("GridInterval", typeof(double), typeof(UPaintCanvas), new UIPropertyMetadata(24.0, new PropertyChangedCallback(OnGridLayoutChanged)));

        public Brush GridStroke
        {
            get { return (Brush)GetValue(GridStrokeProperty); }
            set { SetValue(GridStrokeProperty, value); }
        }

        public static readonly DependencyProperty GridStrokeProperty =
            DependencyProperty.Register("GridStroke", typeof(Brush), typeof(UPaintCanvas), new UIPropertyMetadata(Brushes.LightBlue, new PropertyChangedCallback(OnGridAppearanceChanged)));


        public Boolean EnableSelection = false;

        #endregion

        #region Private Fields

        Dictionary<int, Stroke> strokeMap = new Dictionary<int, Stroke>();

        #endregion

        #region Private Methods

        private void UPaintCanvas_EditingModeChanged(object sender, RoutedEventArgs e)
        {
            this.selectLine = null;
        }

        private void UPaintCanvas_InputReceived(UTable.Input.InputEventArgs args)
        {
            if (args is MultiTouchEventArgs)
                OnMultiTouchReceived(args as MultiTouchEventArgs);
        }

        private void OnMultiTouchReceived(MultiTouchEventArgs args)
        {
            foreach (FingerEventArgs f in args.FingerEvents)
            {
                switch (EditingMode)
                {
                    case InkCanvasEditingMode.Ink:
                        OnPainting(f);
                        args.Handled = true;
                        args.RouteDown = false;
                        break;
                    case InkCanvasEditingMode.EraseByPoint:
                        OnEraseringPoints(f);
                        args.Handled = true;
                        args.RouteDown = false;
                        break;
                    case InkCanvasEditingMode.EraseByStroke:
                        OnEraseringStrokes(f);
                        args.Handled = true;
                        args.RouteDown = false;
                        break;
                    case InkCanvasEditingMode.Select:
                        args.Handled = true;
                        args.RouteDown = false;
                        OnSelecting(f);
                        break;
                    case InkCanvasEditingMode.GestureOnly:
                        args.Handled = true;
                        args.RouteDown = false;
                        OnViewing(f);
                        break;
                }
            }
        }

        /// <summary>
        /// Handle paint input
        /// </summary>
        private void OnPainting(FingerEventArgs args)
        {
            switch (args.EventType)
            {
                case FingerEventType.FINGER_DOWN_EVENT:
                    if (!strokeMap.ContainsKey(args.FingerID))
                    {
                        System.Windows.Input.StylusPointCollection points = new System.Windows.Input.StylusPointCollection();
                        points.Add(new System.Windows.Input.StylusPoint(args.Position.X, args.Position.Y));
                        Stroke stroke = new Stroke(points);
                        stroke.DrawingAttributes = this.DefaultDrawingAttributes;
                        strokeMap.Add(args.FingerID, stroke);
                        this.Strokes.Add(stroke);
                    }
                    break;
                case FingerEventType.FINGER_MOVE_EVENT:
                    if (strokeMap.ContainsKey(args.FingerID))
                    {
                        strokeMap[args.FingerID].StylusPoints.Add(new System.Windows.Input.StylusPoint(args.Position.X, args.Position.Y));
                    }
                    break;
                case FingerEventType.FINGER_UP_EVENT:
                case FingerEventType.FINGER_OUT_EVENT:
                    if (OnEventOccurred != null && strokeMap.ContainsKey(args.FingerID))
                    {
                        OnEventOccurred(this, new UPaintCanvasEventArgs(UPaintCanvasEventArgs.EventType.LineEnd, strokeMap[args.FingerID], args.FingerID));
                    }
                    if (strokeMap.ContainsKey(args.FingerID))
                        strokeMap.Remove(args.FingerID);
                    break;
            }
        }

        /// <summary>
        /// Handle eraser points input
        /// </summary>
        private void OnEraseringPoints(FingerEventArgs args)
        {
            switch (args.EventType)
            {
                case FingerEventType.FINGER_DOWN_EVENT:
                case FingerEventType.FINGER_MOVE_EVENT:
                    Rect rect = new Rect(args.Position, new Size(this.DefaultDrawingAttributes.Width, this.DefaultDrawingAttributes.Height));
                    Strokes.Erase(rect);
                    break;
            }
        }

        /// <summary>
        /// Handle eraser strokes input
        /// </summary>
        private void OnEraseringStrokes(FingerEventArgs args)
        {
            switch (args.EventType)
            {
                case FingerEventType.FINGER_DOWN_EVENT:
                case FingerEventType.FINGER_MOVE_EVENT:
                    StrokeCollection strokes = Strokes.HitTest(args.Position);
                    Strokes.Remove(strokes);
                    //TODO: raise line remove event
                    break;
            }
        }

        /// <summary>
        /// Handle select input
        /// </summary>
        private void OnSelecting(FingerEventArgs args)
        {

        }

        /// <summary>
        /// Handle view input
        /// </summary>
        private void OnViewing(FingerEventArgs args)
        {

        }

        private void OnGestureDetected(UTable.Objects.Handlers.Gestures.GestureRecognizer sender, GestureDetectedEventArgs args)
        {
            Gesture gesture = args.DetectedGesture;

            if (gesture is TapGesture)
            {
                HandleTap(gesture as TapGesture);
            }
            else if (gesture is TranslateGesture)
            {
                HandleTranslate(gesture as TranslateGesture);
            }
            else if (gesture is ZoomGesture)
            {
                HandleZoom(gesture as ZoomGesture);
            }
            else if (gesture is RotateGesture)
            {
                HandleRotate(gesture as RotateGesture);
            }
        }

        private void HandleTap(TapGesture gesture)
        {
            if (EditingMode == InkCanvasEditingMode.Select)
            {
                StrokeCollection selectStrokes = Strokes.HitTest(gesture.Position);
                Select(selectStrokes);
            }

        }

        Polyline selectLine;
        bool isSelecting;

        private void HandleTranslate(TranslateGesture gesture)
        {
              if (EditingMode == InkCanvasEditingMode.Select)
              {
                  switch (gesture.Type)
                  {
                      case TranslateGesture.TranslateType.Start:
                          isSelecting = true;
                          if (GetSelectedStrokes().GetBounds().Contains(gesture.Position))
                          {
                              isSelecting = false;
                          }
                          if (isSelecting)
                          {
                              selectLine = new Polyline();
                              selectLine.Stroke = Brushes.Red;
                              selectLine.StrokeThickness = 3;
                              selectLine.StrokeDashCap = PenLineCap.Round;
                              DoubleCollection dashArray = new DoubleCollection();
                              dashArray.Add(4);
                              dashArray.Add(4);
                              selectLine.StrokeDashArray = dashArray;
                              selectLine.Points.Add(gesture.Position);
                              this.Children.Add(selectLine);
                          }
                          break;
                      case TranslateGesture.TranslateType.Move:
                          if (isSelecting)
                          {
                              selectLine.Points.Add(gesture.Position);
                          }
                          else
                          {
                              TranslateTransform translate = new TranslateTransform();
                              translate.X = gesture.Offset.X;
                              translate.Y = gesture.Offset.Y;
                              GetSelectedStrokes().Transform(translate.Value, false);
                          }
                          break;
                      case TranslateGesture.TranslateType.End:
                          if (isSelecting)
                          {
                              StrokeCollection selectStrokes = Strokes.HitTest(selectLine.Points, 80);
                              Select(selectStrokes);
                              this.Children.Remove(selectLine);
                          }
                          break;
                  }
              }
        }

        private void HandleZoom(ZoomGesture gesture)
        {
            if (EditingMode == InkCanvasEditingMode.Select)
            {
                if (GetSelectedStrokes().GetBounds().Contains(gesture.ZoomCenter))
                {
                    Rect rect = GetSelectedStrokes().GetBounds();
                    ScaleTransform scale = new ScaleTransform();
                    scale.ScaleX = gesture.ZoomFactor;
                    scale.ScaleY = gesture.ZoomFactor;
                    scale.CenterX = rect.X + rect.Width / 2;
                    scale.CenterY = rect.Y + rect.Height / 2;
                    GetSelectedStrokes().Transform(scale.Value, false);
                }
            }
        }

        private void HandleRotate(RotateGesture gesture)
        {
            if (EditingMode == InkCanvasEditingMode.Select)
            {
                if (GetSelectedStrokes().GetBounds().Contains(gesture.RotateCenter))
                {
                    Rect rect = GetSelectedStrokes().GetBounds();
                    RotateTransform rotate = new RotateTransform();
                    rotate.Angle = gesture.RotateAngle;
                    rotate.CenterX = rect.X + rect.Width / 2;
                    rotate.CenterY = rect.Y + rect.Height / 2;
                    GetSelectedStrokes().Transform(rotate.Value, false);
                }
            }
        }

        public void AddNewStroke(List<Point> line, Color lineColor)
        {
            System.Windows.Input.StylusPointCollection points = new System.Windows.Input.StylusPointCollection();
            foreach (Point p in line)
                points.Add(new System.Windows.Input.StylusPoint(p.X, p.Y));
            Stroke stroke = new Stroke(points);
            stroke.DrawingAttributes.FitToCurve = true;
            stroke.DrawingAttributes.IsHighlighter = true;
            stroke.DrawingAttributes.Color = lineColor;
            stroke.DrawingAttributes.Width = DefaultDrawingAttributes.Width;
            Strokes.Add(stroke);
        }

        public void ClearStrokes()
        {
            Strokes.Clear();
        }

        #endregion

        #region Properties

        public event UPaintCanvasEventHandler OnEventOccurred;

        #endregion

        /// <summary>
        /// The code surrounded by "IControl Implementation" region implements the IControl interface.
        /// 
        /// Note: this code should not be changed
        /// </summary>
        #region IControl Implementation

        #region Private Fields

        private ControlBaseImpl _impl;

        #endregion

        #region Private Methods

        private void InitializeIControlPart()
        {
            _impl = new ControlBaseImpl(this);
            _impl.GotFingerCapture += new UTable.Input.MultiTouch.FingerEventHandler(_impl_GotFingerCapture);
            _impl.LostFingerCapture += new UTable.Input.MultiTouch.FingerEventHandler(_impl_LostFingerCapture);
            this.InputReceived += new UTable.Input.InputEventHandler(_impl_InputReceived);
        }

        void _impl_InputReceived(UTable.Input.InputEventArgs args)
        {
            if (args is UTable.Input.MultiTouch.MultiTouchEventArgs)
                MultiTouchProcessor.RaiseMultiTouchEvent(args as UTable.Input.MultiTouch.MultiTouchEventArgs);
        }

        void _impl_LostFingerCapture(object sender, UTable.Input.MultiTouch.FingerEventArgs args)
        {
            if (LostFingerCapture != null)
                LostFingerCapture(sender, args);
        }

        void _impl_GotFingerCapture(object sender, UTable.Input.MultiTouch.FingerEventArgs args)
        {
            if (GotFingerCapture != null)
                GotFingerCapture(sender, args);
        }

        #endregion

        #region IControl Members

        public UTable.Objects.Controls.ControlInfo Info
        {
            get { return _impl.Info; }
        }

        public UTable.Objects.Handlers.MultiTouchProcessor MultiTouchProcessor
        {
            get { return _impl.MultiTouchProcessor; }
        }

        #endregion

        #region IVisualNode Members

        public String ID
        {
            get { return _impl.ID; }
        }

        public new double Width
        {
            get { return _impl.Width; }
            set { _impl.Width = value; }
        }

        public new double Height
        {
            get { return _impl.Height; }
            set { _impl.Height = value; }
        }

        public new bool IsHitTestVisible
        {
            get { return _impl.IsHitTestVisible; }
            set { _impl.IsHitTestVisible = value; }
        }

        #endregion

        #region IInputNode Members

        public void RaiseEvent(UTable.Input.InputEventArgs e)
        {
            if (InputReceived != null)
            {
                InputReceived(e);
            }
            OnInputReceived(e);
        }

        public virtual void OnInputReceived(UTable.Input.InputEventArgs args)
        {
            _impl.OnInputReceived(args);
        }

        public bool CaptureFinger(int fingerId)
        {
            return _impl.CaptureFinger(fingerId);
        }

        public bool UncaptureFinger(int fingerId)
        {
            return _impl.UncaptureFinger(fingerId);
        }

        public event UTable.Input.MultiTouch.FingerEventHandler GotFingerCapture;

        public event UTable.Input.MultiTouch.FingerEventHandler LostFingerCapture;

        public event UTable.Input.InputEventHandler InputReceived;

        #endregion
        #endregion
    }


    public class UPaintCanvasEventArgs
    {
        public UPaintCanvasEventArgs(EventType type, Stroke stroke, int id)
        {
            this.Type = type;
            this.Stroke = stroke;
            this.LineID = id;
        }

        public UPaintCanvasEventArgs(EventType type, Polyline line, int id)
        {
            this.Type = type;
            this.Line = line;
            this.LineID = id;
        }

        public enum EventType
        {
            LineStart,
            LineUpdate,
            LineEnd,
            LineRemove,
        }

        public EventType Type { get; protected set; }

        public Polyline Line { get; protected set; }

        public Stroke Stroke { get; protected set; }

        public int LineID { get; protected set; }
    }

    public delegate void UPaintCanvasEventHandler(UPaintCanvas sender, UPaintCanvasEventArgs args);
}
